根據前面的 component 的寫法, React 會是比較直接替代方案,並且順便加上 Typescript。
將 API 回傳的 Container Node 整理成:
export class Node {
// component's prop
props: any
children: Node[]
constructor(props: any) {
this.props = props
this.children = []
}
}
並且準備好 API Body Type,和 Upload File Function Type:
export interface UpdateEvent {
state_id?: string
id?: string
value?: any
// 給 Button 用的【是否是暫時性的變動】
is_temp?: boolean
}
就可以把 Component Typescript Function Parameter 寫成:
export interface Props {
node: Node
// component's callback
// rerun api
update: (event: UpdateEvent) => void
// fileupload api
upload: (file: File) => Promise<Response>
}
例如 Container DOM creator 可以改寫成:
export function TContainer({ node, update, upload }: Props) {
return (
<div id={node.props.id}>
{
node.children.map(child =>
<TComponent node={child}
update={update}
upload={upload} />
)
}
</div>
)
}
對於原本的 Component Factory Function 也可以做以下更動:
const creatorMap: { [id: string]: ((props: Props) => JSX.Element) } = {
textbox_component: TTextbox,
// ...
}
export function TComponent({ node, update, upload }: Props) {
if (!(node.props.name in creatorMap)) {
throw new Error(`unsupported component type: ${node.props.name}`);
}
return creatorMap[node.props.name]({ node, update, upload })
}
改成 React 最大的好處就是原本麻煩而且不是給人看的 DOM 操作 Code 可以寫成得很好看,
不過要注意到,有涉及 State 的地方,例如 Textbox ,之前為了避免戳 api 更新時把使用者輸入的資料洗掉,我們會把 component value 存在一個 global dictionary。
現在在使用 React ,我們要用到 react state 去保留 textbox value。
export function TTextbox(...) {
const [value, setValue] = useState<string>(stateValues[node.props.id] || '')
// ...
return
// ...
<input type="text"
className="input"
id={node.props.id}
value={value}
onChange={(event) => {
stateValues[event.target.id] = event.target.value
setValue(event.target.value)
}}
onBlur={(event) => {
update({
id: event.target.id,
value: stateValues[event.target.id],
})
}}/>
// ...
}
兩層 State 分別有不同的作用: